package org.anderground.app.storage.db;

import java.io.IOException;
import java.util.HashMap;
import java.util.Set;

import org.anderground.app.storage.ConfigData;
import org.anderground.app.storage.ConfigWriter;
import org.anderground.app.storage.OnProgressCallback;
import org.anderground.app.storage.ConfigData.ConfigCity;
import org.anderground.app.storage.db.DbStorage.CitiesTable;
import org.anderground.app.storage.db.DbStorage.CityAliasesTable;
import org.anderground.app.storage.db.DbStorage.CountriesTable;
import org.anderground.core.model.Country;
import org.anderground.core.storage.StorageException;
import org.anderground.core.util.Coord;

import android.database.DatabaseUtils.InsertHelper;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

/**
 * @author Eldar Abusalimov
 */
public class DbConfigWriter extends AbstractDbWriter<ConfigData> implements
		ConfigWriter {

	private OnProgressCallback callback;

	/**
	 * 
	 * @param db
	 * @param onProgressCallback
	 */
	public DbConfigWriter(SQLiteDatabase db,
			OnProgressCallback onProgressCallback) {
		super(db);
		callback = onProgressCallback;
	}

	/**
	 * 
	 * @param openHelper
	 * @param onProgressCallback
	 */
	public DbConfigWriter(SQLiteOpenHelper openHelper,
			OnProgressCallback onProgressCallback) {
		super(openHelper);
		callback = onProgressCallback;
	}

	@Override
	protected void writeToOutput(ConfigData data, SQLiteDatabase db)
			throws IOException {

		HashMap<Country, Long> countriesRowsMap = insertCountries(db, data,
				getOnProgressCallback());
		if (countriesRowsMap == null) {
			throw new StorageException("unable to insert the countries");
		}

		if (!insertCities(db, data, countriesRowsMap, getOnProgressCallback())) {
			throw new StorageException("unable to insert the cities");
		}

		db.execSQL("INSERT INTO " + DbStorage.TABLE_SETUP_INFO
				+ " DEFAULT VALUES;");
	}

	/**
	 * Inserts the {@link ConfigData} into the given {@link SQLiteDatabase}.
	 * 
	 * @param db
	 * @param data
	 * @param onProgressCallback
	 * @return the country-to-rowId map of the newly inserted rows, or null if
	 *         an error occurred
	 */
	private static HashMap<Country, Long> insertCountries(SQLiteDatabase db,
			ConfigData data, OnProgressCallback onProgressCallback) {

		if (onProgressCallback != null) {
			onProgressCallback.onProgress("(countries)");
		}

		InsertHelper ih = new InsertHelper(db, CountriesTable.NAME);
		final int tCountries_Name_Col = ih
				.getColumnIndex(CountriesTable.Columns.NAME);
		final int tCountries_Code_Col = ih
				.getColumnIndex(CountriesTable.Columns.CODE);

		HashMap<Country, Long> countriesRowsMap = new HashMap<Country, Long>();

		for (Country country : data.getCountries()) {

			ih.prepareForInsert();

			ih.bind(tCountries_Code_Col, country.getCode());
			ih.bind(tCountries_Name_Col, country.getName());

			long countryRowId = ih.execute();
			if (countryRowId < 0) {
				return null;
			}

			countriesRowsMap.put(country, countryRowId);
		}

		return countriesRowsMap;
	}

	/**
	 * Inserts the {@link ConfigData} into the given {@link SQLiteDatabase}.
	 * 
	 * @param db
	 * @param data
	 * @param countriesRowsMap
	 * @param onProgressCallback
	 * @return true if all of the cities were inserted successfully, false
	 *         otherwise
	 */
	private static boolean insertCities(SQLiteDatabase db, ConfigData data,
			HashMap<Country, Long> countriesRowsMap,
			OnProgressCallback onProgressCallback) {

		InsertHelper cityHelper = new InsertHelper(db, CitiesTable.NAME);
		final int tCities_Id_Col = cityHelper
				.getColumnIndex(CitiesTable.Columns._ID);
		final int tCities_Name_Col = cityHelper
				.getColumnIndex(CitiesTable.Columns.NAME);
		final int tCities_Url_Col = cityHelper
				.getColumnIndex(CitiesTable.Columns.URL);
		final int tCities_CountryId_Col = cityHelper
				.getColumnIndex(CitiesTable.Columns.COUNTRY_ID);
		final int tCities_Latitude_Col = cityHelper
				.getColumnIndex(CitiesTable.Columns.LATITUDE);
		final int tCities_Longitude_Col = cityHelper
				.getColumnIndex(CitiesTable.Columns.LONGITUDE);

		InsertHelper aliasHelper = new InsertHelper(db, CityAliasesTable.NAME);
		final int tCityAliases_Name_Col = aliasHelper
				.getColumnIndex(CityAliasesTable.Columns.NAME);
		final int tCityAliases_CityId_Col = aliasHelper
				.getColumnIndex(CityAliasesTable.Columns.CITY_ID);

		final int size = data.size();
		for (int i = 0; i < size; i++) {
			ConfigCity city = data.valueAt(i);

			Long countryId = countriesRowsMap.get(city.getCountry());
			if (countryId == null) {
				throw new RuntimeException(
						"abnormal: null retrieved from countriesRowsMap");
			}

			cityHelper.prepareForInsert();
			Coord location = city.getLocation();

			cityHelper.bind(tCities_Id_Col, city.getId());
			cityHelper.bind(tCities_CountryId_Col, countryId);
			cityHelper.bind(tCities_Name_Col, city.getName());
			cityHelper.bind(tCities_Url_Col, city.getUrl().toExternalForm());
			cityHelper.bind(tCities_Latitude_Col, location.getX());
			cityHelper.bind(tCities_Longitude_Col, location.getY());

			long cityRowId = cityHelper.execute();
			if (cityRowId < 0) {
				return false;
			}
			Set<String> aliases = city.getAliases();
			for (String alias : aliases) {

				aliasHelper.prepareForInsert();

				aliasHelper.bind(tCityAliases_CityId_Col, cityRowId);
				aliasHelper.bind(tCityAliases_Name_Col, alias);

				if (aliasHelper.execute() < 0) {
					return false;
				}
			}
			if (onProgressCallback != null) {
				onProgressCallback.onProgress(i + 1, size, city.getName());
			}
		}

		return true;
	}

	/**
	 * @return the callback
	 */
	protected final OnProgressCallback getOnProgressCallback() {
		return callback;
	}

}
